/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 *
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package info.rflow.graph;

import info.rflow.Utils;
import info.rflow.actionproviders.BackLabelPopUpProvider;
import info.rflow.actionproviders.GraphSceneMenu;
import info.rflow.actionproviders.GraphWidgetSelector;
import info.rflow.actionproviders.NodePopUpProvider;
import info.rflow.graph.model.DataEdge;
import info.rflow.graph.model.DataPin;
import info.rflow.graph.model.RBlockNode;
import info.rflow.graph.util.GraphUtils;
import info.rflow.graph.util.ObjectSceneListenerAdapter;
import info.rflow.graph.util.SwitchableMoveStratgy;
import info.rflow.model.BlockStatusListener;
import info.rflow.model.RBlock;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.anchor.AnchorFactory;
import org.netbeans.api.visual.anchor.AnchorShape;
import org.netbeans.api.visual.anchor.PointShape;
import org.netbeans.api.visual.graph.GraphPinScene;
import org.netbeans.api.visual.graph.layout.GridGraphLayout;
import org.netbeans.api.visual.layout.LayoutFactory;
import org.netbeans.api.visual.layout.SceneLayout;
import org.netbeans.api.visual.model.ObjectSceneEvent;
import org.netbeans.api.visual.model.ObjectSceneEventType;
import org.netbeans.api.visual.model.ObjectState;
import org.netbeans.api.visual.router.Router;
import org.netbeans.api.visual.router.RouterFactory;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.LabelWidget;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Widget;
import org.openide.util.Utilities;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;


/**
 * @author David Kaspar
 */
public class RWorkflowScene extends GraphPinScene<RBlockNode, DataEdge, DataPin> {

    private LayerWidget mainLayer = new LayerWidget(this);
    private LayerWidget connectionLayer = new LayerWidget(this);
    private LayerWidget interractionLayer = new LayerWidget(this);
    private LayerWidget backgroundLayer = new LayerWidget(this);

    private SwitchableMoveStratgy switchableMoveStratgy = new SwitchableMoveStratgy();

    private WidgetAction moveAction = ActionFactory.createMoveAction(switchableMoveStratgy, null);
    private WidgetAction nodePopUpAction = ActionFactory.createPopupMenuAction(new NodePopUpProvider(this));
    //    private WidgetAction connectAction = ActionFactory.createExtendedConnectAction(interractionLayer, connectProvider);
//    private WidgetAction reconnectAction = ActionFactory.createReconnectAction(new GraphReconnectProvider(this));
    private WidgetAction moveControlPointAction = ActionFactory.createFreeMoveControlPointAction();
    private WidgetAction selectAction = ActionFactory.createSelectAction(new GraphWidgetSelector(this));
//    private WidgetAction inplaceEditAction = ActionFactory.createInplaceEditorAction(inplaceProvider);
//    private WidgetAction acceptAction = ActionFactory.createAcceptAction(acceptProvider);

    //    public static final Image NODE_IMAGE = Utilities.loadImage("test/resources/custom_displayable_32.png");
    public static final Image NODE_IMAGE = Utilities.loadImage("info/rflow/icons/node_icon2.png");
    //    private Router router = RouterFactory.createOrthogonalSearchRouter(mainLayer, connectionLayer, interractionLayer);
//    private Router router = RouterFactory.createOrthogonalSearchRouter(mainLayer, connectionLayer);
    private Router router = RouterFactory.createFreeRouter();

    private SceneLayout sceneLayout;

    public static final String ZOOM_FACTOR = "scene.zoom.factor";


    public RWorkflowScene() {
        addChild(backgroundLayer);
        addChild(mainLayer);
        addChild(connectionLayer);
        addChild(interractionLayer);

        createView();

        getActions().addAction(ActionFactory.createRectangularSelectAction(this, backgroundLayer));
        getActions().addAction(ActionFactory.createPanAction());
        getActions().addAction(ActionFactory.createZoomAction());
        getActions().addAction(ActionFactory.createPopupMenuAction(new GraphSceneMenu(this)));
        GraphUtils.initGrids(this);

//        getActions().addAction(acceptAction);

        sceneLayout = LayoutFactory.createSceneGraphLayout(this, new GridGraphLayout<RBlockNode, DataEdge>());

        addObjectSceneListener(new ObjectSceneListenerAdapter() {

            public void objectStateChanged(ObjectSceneEvent event, Object changedObject, ObjectState previousState, ObjectState newState) {
                super.objectStateChanged(event, changedObject, previousState, newState);
            }
        }, ObjectSceneEventType.OBJECT_STATE_CHANGED);


        // make the zoom factor persistent
        addSceneListener(new SceneListener() {
            @Override
            public void sceneRepaint() {
            }


            @Override
            public void sceneValidating() {
                Utils.getPrefs().putDouble(ZOOM_FACTOR, RWorkflowScene.this.getZoomFactor());
            }


            @Override
            public void sceneValidated() {
            }
        });
        setZoomFactor(Utils.getPrefs().getDouble(ZOOM_FACTOR, 2));

    }


    protected Widget attachNodeWidget(final RBlockNode node) {
        final RBlockWidget widget = new RBlockWidget(this, node);
//        Boolean isExecuted = node.getRBlock().isExecuted();
//        if (isExecuted == null)
//            widget.setStatus(RBlockWidget.NodeStatus.NOT_READY);
//        else
//            widget.setStatus(isExecuted ? RBlockWidget.NodeStatus.EXECUTED : RBlockWidget.NodeStatus.READY);

        node.getRBlock().addBlockStatusListener(new BlockStatusListener() {
            @Override
            public void executionFinished(RBlock rBlock) {
                //todo continue this
                widget.setStatus(RBlockWidget.NodeStatus.EXECUTED);
                widget.repaint();
                validate();
            }


            @Override
            public void executionStarted(RBlock rBlock) {
                widget.setStatus(RBlockWidget.NodeStatus.EXECUTING);
                widget.repaint();
                validate();
            }


            @Override
            public void blockReset(RBlock rBlock) {
                widget.setStatus(RBlockWidget.NodeStatus.READY);
            }
        });

        widget.setLayout(LayoutFactory.createVerticalFlowLayout(LayoutFactory.SerialAlignment.CENTER, 4));
        widget.setImage(NODE_IMAGE);
        widget.setLabel(node.getInstanceName());

        widget.setToolTipText("Class: " + node.getInstanceName());
//        widget.getLabelWidget().getActions().addAction(inplaceEditAction);

        widget.getActions().addAction(selectAction);
        widget.getActions().addAction(moveAction);
        widget.getActions().addAction(createObjectHoverAction());
        widget.getActions().addAction(nodePopUpAction);
//        widget.getActions().addAction(connectAction);


        mainLayer.addChild(widget);

        return widget;
    }


    protected Widget attachPinWidget(RBlockNode node, DataPin pin) {
        PortWidget portWidget = new PortWidget(this, pin.toString());

//        if(pin.toString().equals("in")) {
//            portWidget.setPreferredLocation(new Point(0, 0));
//        } else {
//            portWidget.setPreferredLocation(new Point(50, 0));
//        }

        RBlockWidget nodeWidget = ((RBlockWidget) findWidget(node));

//        if (pin.getPropName().equals(ConfPin.THIS_NAME)) {
//            nodeWidget.addThisPin(portWidget);
//            portWidget.getActions().addAction(createObjectHoverAction());
//
//        } else {
        nodeWidget.addChild(portWidget); // todo distinguish between properties and the this port here
//        }

//        portWidget.getActions().addAction(createObjectHoverAction());
//        portWidget.getActions().addAction(selectAction);
//        portWidget.getActions().addAction(connectAction);

        portWidget.getActions().addAction(selectAction);
//        portWidget.getImageWidget().getActions().addAction(connectAction);
        return portWidget;
    }


    protected Widget attachEdgeWidget(DataEdge edge) {
        ConnectionWidget c = new ConnectionWidget(this);
        c.setRouter(router);

        c.setToolTipText("Double-click for Add/Remove Control Point");
        c.setTargetAnchorShape(AnchorShape.TRIANGLE_FILLED);
        c.setControlPointShape(PointShape.SQUARE_FILLED_BIG);
        c.setEndPointShape(PointShape.SQUARE_FILLED_BIG);
        connectionLayer.addChild(c);

//        c.getActions().addAction(reconnectAction);
        c.getActions().addAction(createSelectAction());
        c.getActions().addAction(ActionFactory.createAddRemoveControlPointAction());
        c.getActions().addAction(moveControlPointAction);
//        c.getActions().addAction(ActionFactory.createPopupMenuAction(edgeMenu));

        return c;
    }


    protected void attachEdgeSourceAnchor(DataEdge edge, DataPin oldSourcePin, DataPin sourcePin) {
        ConnectionWidget widget = (ConnectionWidget) findWidget(edge);
        Widget sourceNodeWidget = findWidget(sourcePin);
        widget.setSourceAnchor(sourceNodeWidget != null ? AnchorFactory.createDirectionalAnchor(sourceNodeWidget, AnchorFactory.DirectionalAnchorKind.HORIZONTAL) : null);
    }


    protected void attachEdgeTargetAnchor(DataEdge edge, DataPin oldTargetPin, DataPin targetPin) {
        ConnectionWidget widget = (ConnectionWidget) findWidget(edge);
        Widget targetNodeWidget = findWidget(targetPin);
//        widget.setTargetAnchor(targetNodeWidget != null ? AnchorFactory.createRectangularAnchor(targetNodeWidget) : null);
        widget.setTargetAnchor(targetNodeWidget != null ? AnchorFactory.createDirectionalAnchor(targetNodeWidget, AnchorFactory.DirectionalAnchorKind.HORIZONTAL) : null);
    }


    /**
     * Adds and connects an edge to the current graph.
     */
    public void createEdge(DataEdge edge) {
        addEdge(edge);
        setEdgeSource(edge, edge.getSource());
        setEdgeTarget(edge, edge.getTarget());
    }


    /**
     * Invokes layout of the scene.
     */
    public void layoutScene() {
        sceneLayout.invokeLayout();
    }


    public RBlockNode findNodeByName(String instanceName) {
        assert instanceName != null;
        for (RBlockNode node : getNodes()) {
            if (node.getInstanceName().equals(instanceName))
                return node;
        }

        return null;
    }


    public void addBckndLabel(String labelText, Point location, Dimension d, Color c) {
        LabelWidget label = new LabelWidget(this, "Drag border to resize me. \nDrag inner area to move me. \nDouble click inner areato rename me");
        label.setOpaque(true);
        Color labelColor = c == null ? new Color(255, 250, 210) : c;
        label.setBackground(c);
        label.setCheckClipping(true);
        label.setAlignment(LabelWidget.Alignment.LEFT);
        label.setVerticalAlignment(LabelWidget.VerticalAlignment.TOP);
        label.setPreferredLocation(location);
        label.setPreferredSize(d);
        label.getActions().addAction(ActionFactory.createResizeAction());
        label.getActions().addAction(moveAction);
//        label.getActions().addAction(inplaceEditAction);
        label.setBorder(org.netbeans.api.visual.border.BorderFactory.createImageBorder(new Insets(5, 5, 5, 5), Utilities.loadImage("test/resources/shadow_normal.png"))); // NOI18N
        label.getActions().addAction(ActionFactory.createPopupMenuAction(new BackLabelPopUpProvider(label)));

        backgroundLayer.addChild(label);

        validate();
    }


    public void removeBckndLabel(Widget bcndLabelWidget) {
        assert bcndLabelWidget != null && backgroundLayer.getChildren().contains(bcndLabelWidget);

        backgroundLayer.removeChild(bcndLabelWidget);
        validate();
    }


    public List<LabelWidget> getBckndLabels() {
        List<LabelWidget> bckndLabels = new ArrayList<LabelWidget>();
        for (Widget widget : backgroundLayer.getChildren()) {
            if (widget instanceof LabelWidget)
                bckndLabels.add((LabelWidget) widget);
        }

        return bckndLabels;
    }


    public void setSnap2Grid(boolean useGrid) {
        // change only if different to current state
        switchableMoveStratgy.setUseGridStrategy(useGrid);
    }
}
